-
Notifications
You must be signed in to change notification settings - Fork 11
SDK-less Google-SignIn – Part 2 of n #717
Conversation
WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift
Outdated
Show resolved
Hide resolved
| // TODO: We might want to add some of these or them configurable | ||
| // | ||
| // The request we make with the SDK asks for: | ||
| // | ||
| // - profile | ||
| // - https://www.googleapis.com/auth/userinfo.email | ||
| // - https://www.googleapis.com/auth/userinfo.profile | ||
| // - openid | ||
| // | ||
| // See https://developers.google.com/identity/protocols/oauth2/scopes | ||
| ("scope", "https://www.googleapis.com/auth/userinfo.email") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spoiler: I updated this in my WIP branch but haven't seen any difference in the behavior, but that's likely due to my implementation being quite minimal at this point. Or, it's possible that only the backend would notice the difference, when completing the authentication using the OAuth token.
| guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { | ||
| throw URLError( | ||
| .unsupportedURL, | ||
| userInfo: [ | ||
| NSLocalizedDescriptionKey: "Could not create `URLComponents` instance from \(baseURL)" | ||
| ] | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this is acceptable as a URLError, or should I update it to another OAuthError case for consistency?
I'm leaning toward moving it to OAuthError.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's safe to ! here, since we already ! the googleSignInBaseURL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great observation! After all, what are the chances URLComponents will not initialize with a valid input URL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in d749c80
| /// The type of token returned. At this time, this field's value is always set to Bearer. | ||
| let tokenType: String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation comment I lifted from from the Google docs makes me wonder whether there's even a point in storing it at this time?
| // | ||
| // A code_verifier is a high-entropy cryptographic random string using the unreserved | ||
| // characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~", with a minimum length of 43 | ||
| // characters and a maximum length of 128 characters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan to address the completion of this type in a dedicated PR. The process works for development purposes in the current, plain mode.
| func testCodeChallengeInS256ModeIsEncodedAsPerSpec() { | ||
| // TODO: | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put this down as a double reminder to finish the PKCE implementation. See also previous comment.
|
@mokagio I'm okay with how ever you'd like to break down the end-to-end flow. I don't see any difficulties reviewing individual component, low-level or UI. 😄 |
| // error, which we would catch because the unit tests would crash. | ||
| static var googleSignInBaseURL = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")! | ||
|
|
||
| static func googleSignInAuthURL(clientId: String, pkce: ProofKeyForCodeExchange) throws -> URL { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are essentially implement OAuth 2, I think we can replace "Google Sign In" with a more generic term? Like, this function becomes oauth2AuthenticationURL, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm making assumption here, but I imagine the whole Google Sign In workflow can be used in "Sign In via Facebook", or even the WordPress OAuth2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I was thinking about this before going AFK and realized I may be using unclear boundaries here.
You might have noticed I made an OAuth folder and a GoogleSignIn one. My intent was to separate the components that are GoogleSignIn specific from those that are part of the OAuth spec. One issue with how I executed this is that I haven't been cross checking between the OAuth and Google documentations so there might be stuff in the OAuth folder that's still Google specific, and vice versa.
Thinking about it since, I came to the conclusion that I'd prefer to separate OAuth- from Google- specific code only once we'll need to add a new OAuth client (likely WordPress OAuth2). As such, I'm tempted to follow up this PR with one that moves all the OAuth/ code into GoogleSignIn/, renaming some of the types if necessary.
My rationale for the suggestion above is that I'm more interested in having a working version of the SDK-less authentication than a general purpose OAuth client, even though building a client first might make it easier to add further authentication implementation in the future.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me. We probably don't need to "pre-abstract" stuff if there is no immediate plan to support another OAuth provider (service? server?). I trust you'll strike the right balance 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I trust you'll strike the right balance 👍
Thanks for the confidence.
| guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { | ||
| throw URLError( | ||
| .unsupportedURL, | ||
| userInfo: [ | ||
| NSLocalizedDescriptionKey: "Could not create `URLComponents` instance from \(baseURL)" | ||
| ] | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's safe to ! here, since we already ! the googleSignInBaseURL?
| } | ||
|
|
||
| components.queryItems = queryItems | ||
| return try components.asURL() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's alsy safe to ! here too?
Also, this asURL is an Alamofire function, maybe we shouldn't use it here since it throws an AFError?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another good point. Addressed in d5c6fd0
| var components = URLComponents() | ||
| components.queryItems = items | ||
|
|
||
| guard let query = components.query else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's possible to have a nil query in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. That should be the case.
🤔 I guess I'm just uncomfortable force unwrapping stuff, but it does make for simpler code in a case like this one.
I tried to write a test that resulted in this code throwing, but I wasn't able to. That's not to say that there is no way for the failure to occur, of course. Still, I feel a bit more confident about removing the errors here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 8b6f6a8
| throw OAuthError.failedToBuildURLQuery | ||
| } | ||
|
|
||
| guard let data = query.data(using: .utf8) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's possible to fail to utf8 encode the query string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 8b6f6a8 while working on your previous comment.
Thanks!
| // The code verifier should have enough entropy to make it impractical to guess the value. | ||
| struct ProofKeyForCodeExchange { | ||
|
|
||
| enum Mode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not good at naming things, but what do you think about renaming this to Method and the var method to something like var googleOAuthParameterName?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let codeVerifier: String | ||
| let mode: Mode | ||
|
|
||
| init(codeVerifier: String, mode: Mode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering we should randomly generate codeVerifier, do you think we can make this argument nil by default and generate a random string internally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the suggestion. I'm keen on generating the string internally according to the spec but I think that's work that could live in a dedicated PR. Does that sound okay?
What I have in mind is along the lines of your suggestion, with the difference of providing the random string as the default value.
init(codeVerifier: String = makeRandomPKCEString(), method: Method = .s256) {63a01aa to
d749c80
Compare
This also resulted in renaming the computed variable `method` to `testMethodURLQueryParameterValue` because `Method.method` would have been confusing. The new name also better reflects where the value is meant to be used.
Adds objects to model OAuth token request and response, the pre-requisite Proof of Code Encryption Key object for the request that generates the URL to use for the token, and the logic to generate the token request URL.
@crazytonyli one disadvantage of using an incremental approach to building a big feature such as this is that the first PRs fail to tell the story of the system being developed, because they are only individual components not yet used together.
Is this format acceptable for you to review? Otherwise I could:
NewGoogleAuthenticator) so that we can do PRs that add a few bits and also show how they are used; orLet me know what would work best for you. Thanks!
To test
If CI is green, we're good.
See also
CHANGELOG.mdif necessary.